import { Callout } from '@theguild/components'

# Plugin Lifecycle

Plugins are executed in order of their usage, and inject functionality serially, so aim to keep your
plugins simple and standalone as much as possible.

The `before` functions are called in order, and then the `after` function of each hook will be
called in the same order when the phase has been done.

The basic API allows you to keep the JS closure between `before` and `after`, for example:

```ts
const myPlugin = {
  onParse({ params }) {
    // This is the before function

    return ({ result }) => {
      // The is the after function,
      // JS Closure allow me to access the params here as well
    }
  }
}
```

<Callout>
  All plugin lifecycle methods are executed in FIFO order (First-in, First-out) to make timing more
  consistent.
</Callout>

### The Improved `context`

While in regular GraphQL executions, the `context` is built right before `execute` happens, in
`envelop` we allow plugin developers to take part it that context from all phases.

While calling `getEnveloped` function (the result of `envelop({ plugins: [ ... ]}))`, you can pass
any custom object that will be the base for your GraphQL execution `context`.

In most cases, you'll pass the incoming HTTP request (or, just the relevant parts of it) to make it
available for the plugins you use:

```ts
import * as GraphQLJS from 'graphql'
import { envelop, useEngine } from '@envelop/core'

const getEnveloped = envelop({
  plugins: [
    useEngine(GraphQLJS)
    // ... plugins
  ]
})

myHttpServer.on('request', async req => {
  const { parse, validate, contextFactory, execute, schema } = getEnveloped({ req })
  // ...
})
```

Plugins might also use this `context` to keep a contextual reference for objects they might need
across stages.

So for example, if you wish to make some data from `parse` stage available for you in `execute`
phase, you can extend the context while running `onParse` and then access that data in `onExecute`:

```ts
const myPlugin = {
  onParse({ extendContext }) {
    extendContext({
      myVar: 'test'
    })
  },
  onExecute({ args }) {
    const myVar = args.contextValue.myVar
  }
}
```

## Plugins API

You can find the
[complete signature for plugins API here](https://github.com/graphql-hive/envelop/blob/d4c8f90f9fbccf3032b3b3b05f359b691076d25b/packages/types/src/index.ts#L55).

### `onPluginInit(api)`

This method is called only once when the plugin is being initialized. This hook is triggered only
once and is useful for setting up the plugin.

#### API

- `setSchema` - sets the initial schema to be used.
- `plugins` - list of all other loaded plugins.
- `addPlugin` - adds a plugin to the list of plugins.

### `onEnveloped(api)`

This method is called every time `getEnveloped` is called, and per request. This is useful if you
need to do some setup right before an incoming execution flow.

#### API

- `setSchema` - sets the initial schema to be used.
- `context` - the initial context object passed to `getEnveloped`, including the context built by
  plugins that ran before.
- `extendContext` - extends the initial context object with additional fields.

### `onParse(api)`

Called every time an operation is being executed.

#### `before`

- `params` - the original parameters passes to `parse` function.
- `parseFn` - the current `parse` function. By default, it's the one from `graphql` package.
- `setParseFn` - Replaces the `parse` function with a custom function.
- `setParsedDocument` - sets the parse result. Setting this will skip calling `parse` for the
  executed operation.
- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

#### `after`

- `result` - the result `DocumentNode` of the parsing function.
- `replaceParseResult` - replaces the parsed result.
- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

### `onValidate(api)`

Called every time an operation is being executed.

#### `before`

- `params` - the original parameters passes to `validate` function.
- `validateFn` - the current `validate` function. By default, it's the one from `graphql` package.
- `setValidationFn` - Replaces the `validate` function with a custom function.
- `addValidationRule` - Adds a validation rule to the list of default validation rules (as defined
  in `graphql` package).
- `setResult` - sets the validation result. Setting this will skip calling `validate` for the
  executed operation.
- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

#### `after`

- `valid` - A boolean indicates if the validation passed.
- `result` - `null` in case of a valid document, otherwise an array of validation errors.
- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

### `onContext(api)`

Called every time an operation is being executed. Used for building the GraphQL context
incrementally.

#### `before`

- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

#### `after`

- `context` - the eventual built context, by all plugins.
- `extendContext` - extends the context object with additional fields.

### `onExecute(api)`

Called every time an operation is being executed. The return value of this function is extended and
allows you to hook into resolver calls if needed.

#### `before`

- `args` - arguments pass to `execute` (contains the document, variables and everything else that is
  needed for executing the GraphQL operation)
- `executeFn` - the `execute` function to use, by default it's the one from `graphql` package.
- `setExecuteFn` - replaces the `execute` function.
- `setResultAndStopExecution` - sets the result of the execution immediately. Calling this function
  will stop execution.
- `extendContext` - allow you to extend the context before executing the operation.

You can return an `object` from that function, with the following fields:

#### `onExecuteDone`

Triggered when the execution of the operation is done.

- `result` - the execution result, or AsyncIterable in case of stream response.
- `setResult` - replaces the result. can either be with data or errors.

Since `envelop` aims to support stream responses (for live queries, or `@stream/@defer`), the
`result` might be an `AsyncIterable` of multiple execution results.

If you wish your plugin to support this, please make sure to use the
`handleStreamOrSingleExecutionResult` helper, like that:

```ts
import { handleStreamOrSingleExecutionResult, Plugin } from '@envelop/types'

const myPlugin = (): Plugin => {
  return {
    onExecute({ args }) {
      return {
        onExecuteDone(payload) {
          return handleStreamOrSingleExecutionResult(payload, ({ result, setResult }) => {
            // Here you can access the result, and modify it with setResult if needed
          })
        }
      }
    }
  }
}
```

Alternately, if you don't need to support stream responses, you can use the `isAsyncIterable`
function as a type-guard:

```ts
import { isAsyncIterable, Plugin } from '@envelop/types'

const myPlugin = (): Plugin => {
  return {
    onExecute({ args }) {
      return {
        onExecuteDone({ result, setResult }) {
          if (!isAsyncIterable(result)) return
          // Here you can access result, and modify it with setResult if needed
        }
      }
    }
  }
}
```

### `onSubscribe(api)`

Called every time a `subscription` operation is being executed. The return value of this function is
extended, and allow you to hooks into resolver calls if needed.

#### `before`

- `args` - arguments passes to `subscribe` (contains the document, variables and everything else
  that needed for executing the GraphQL operation)
- `subscribeFn` - the `subscribe` function to use, by default it's the one from `graphql` package.
- `setSubscribeFn` - replaces the `subscribe` function.
- `extendContext` - allow you to extend the context before executing the operation.
- `setResultAndStopExecution` - sets the result of the execution immediately. Calling this function
  will stop execution.

You can return an `object` from that function, with the following fields:

#### `onSubscribeResult`

Triggered when subscription result is being emitted from a `subscription` execution.

- `result` - the subscription result.
- `setResult` - replaces the result. can either be with data or errors.

### `onSchemaChange(api)`

`envelop` allow you to manage a reference to a schema, that you can later access and use within your
server.

Some plugins (like gateway implementations) could potentially change the schema while running, so
`envelop` will trigger that event in case of a schema change _after all plugins have initialized_.

#### API

- `schema` - the `GraphQLSchema`
- `replaceSchema` - replaces the schema. Calling this will trigger `onSchemaChange` for all other
  plugins (except for the one that initiated the change);

### `instrumentation`

An optional `instrumentation` instance can be present in the plugin.

This `Instrumentation` instance allows wrapping an entire phase execution (including all plugin
hooks); meaning, running code just before, just after and around the execution of the phase.

Instrumentation don't have access to the input and outputs of a phase, use hooks to access that
data. If needed, we recommend to share data between instrumentation and hooks with a `WeakMap` and
the given `context` as the key.

All instrumentation take 2 parameters:

- `payload`: an object containing the graphql context.
- `wrapped`: The function representing the execution of the phase. It takes no parameters, and
  returns `void` (or `Promise<void>` for asynchronous phases). **This function must always be
  called**.

#### Instrumentation composition

If multiple plugins have `instrumentation`, they are composed in the same order as they are defined
in the plugin array (from top to bottom).

It is possible to customize this composition if it doesn't suit your needs (ie. you need hooks and
instrumentation to have a different order of execution).

```ts
import { composeInstrumentation, envelop } from '@envelop/core'

const { instrumentation: instrumentation1, ...plugin1 } = usePlugin1()
const { instrumentation: instrumentation2, ...plugin2 } = usePlugin2()

const instrumentation = composeInstrumentation([instrumentation2, instrumentation1])

const getEnveloped = envelop({
  plugin: [{ instrumentation }, plugin1, plugin2]
})
```

#### `init`

Wraps the envelop (the call to `envelop` function) initialisation.

This includes all the plugins `onEnveloped` hooks, and the creation of the Envelop Orchestrator.

This instrument must be synchronous, the wrapped function is always synchronous.

#### `parse`

Wraps the parse phase. This includes all the plugins `onParse` hooks and the actual `parse`
function.

This instrument must be synchronous, the wrapped function is always synchronous.

#### `validate`

Wraps the validate phase. This includes all the plugins `onValidate` hooks and the actual `validate`
function.

This instrument must be synchronous, the wrapped function is always synchronous.

#### `context`

Wraps the context building phase. This includes all the plugins `onContextBuilding` hooks.

This instrument must be synchronous, the wrapped function is always synchronous.

#### `execute`

Wraps the execute phase. This includes all the plugins `onExecute` hooks.

This instrument can be asynchronous, the wrapped function **can be** asynchronous. Be sure to
`await` or use `.then` on the result of the `wrapped` function to run code after the `execute`
phase.

Note that `wrapped` is not guaranted to return a promise.

#### `subscribe`

Wraps the subscribe phase. This includes all the plugins `onSubsribe` hooks. Note that it doesn't
wrap the entire lifetime of the subscription, but only it's intialisation.

This instrument can be asynchronous, the wrapped function **can be** asynchronous. Be sure to
`await` or use `.then` on the result of the `wrapped` function to run code after the `subsribe`
phase.

Note that `wrapped` is not guaranted to return a promise.
